/**
 * Created on Mar 13, 2007
 *
 * A sparse 2D array. This will be used by the SparseDataSet,
 * which is needed for the new SMR Details window.
 *
 * @author Christopher Brady
 */
package org.ddevil.data;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;


/**
 *
 * @author ?
 * @param <E>
 */
public class Sparse2dArray<E> implements Serializable {

	protected StringPool stringPool;

	protected ArrayList<LinkedList<ListNode<E>>> rows;

	protected int colCount;

	/**
	 * Creates a sparse 2D array
	 */
	public Sparse2dArray(int colCount) {
		this(new StringPool(1000), colCount);
	}

	/**
	 * Creates a sparse 2D array with a string pool.
	 */
	public Sparse2dArray(StringPool stringPool, int colCount) {
		this.stringPool = stringPool;
		rows = new ArrayList<LinkedList<ListNode<E>>>();
		this.colCount = colCount;
	}

	/**
	 * Add a row of data to this array
	 */
	public void add(List<E> data) {
		LinkedList<ListNode<E>> rowData = getLinkedListFromList(data);

		rows.add(rowData);
	}

	/**
	 * Remove a row.
	 */
	public void remove(int row) {
		rows.remove(row);
	}

	/**
	 * Sets row data
	 */
	public void setData(int row, List<E> data) {
		LinkedList<ListNode<E>> rowData = getLinkedListFromList(data);

		rows.set(row, rowData);
	}

	/**
	 * Sets column data
	 */
	public void setData(int row, int col, E data) {
		LinkedList<ListNode<E>> rowData = rows.get(row);

		ListIterator<ListNode<E>> iterator = rowData.listIterator();

		// Loop through the list and insert the node in the right place.
		// This will keep the list ordered so that searches will be faster.
		boolean inserted = false;
		while(iterator.hasNext()) {
			ListNode<E> node = iterator.next();
			int currentCol = node.getCol();

			if(currentCol == col) {
				// Found the node, so it should be updated
				node.setData(data);
				inserted = true;
				break;

			} else if(currentCol > col) {
				// Didn't find the node so insert it
				iterator.previous();
				iterator.add(new ListNode<E>(col, data));
				inserted = true;
				break;
			}
		}

		// If the data was not inserted into the list,
		// add it to the end of the list
		if(!inserted) {
			rowData.add(new ListNode<E>(col, data));
		}


		// Debug code
		/*iterator = rowData.listIterator();
		while(iterator.hasNext()) {
			ListNode<E> node = iterator.next();
			int colNum = node.getCol();
			Object asdf = node.getData();
			System.out.println("Col: " + colNum + "  Data: " + asdf);
		}
		System.out.println("---------------------------------------------");
		System.out.println("");*/
	}

	/**
	 * Returns a row of data as a ArrayList.
	 */
	public ArrayList<E> getData(int row) {
		LinkedList<ListNode<E>> rowData = rows.get(row);
		ArrayList<E> returnData = new ArrayList<E>(colCount);

		for(int i=0; i<colCount; i++) {
			returnData.add(null);
		}

		// Set the data in the vector
		ListIterator<ListNode<E>> iterator = rowData.listIterator();
		while(iterator.hasNext()) {
			ListNode<E> node = iterator.next();
			returnData.set(node.getCol(), node.getData());
		}

		return returnData;
	}

	/**
	 * Returns data at row/col
	 */
	public E getData(int row, int col) {
		E returnValue = null;

		// Find the data in the linked list
		LinkedList<ListNode<E>> rowData = rows.get(row);
		ListIterator<ListNode<E>> iterator = rowData.listIterator();
		while(iterator.hasNext()) {
			ListNode<E> node = iterator.next();

			if(node.getCol() == col) {
				returnValue = node.getData();
				break;
			} else if(node.getCol() > col) {
				// The list is ordered, so break if we are past col
				break;
			}
		}

		return returnValue;
	}

	/**
	 * Clears all data
	 */
	public void clear() {
		rows.clear();
	}

	public boolean equals(Object o) {
		return rows.equals(o);
	}

	public int hashCode() {
		return rows.hashCode();
	}

	/**
	 * Returns true if there is no data.
	 */
	public boolean isEmpty() {
		return rows.isEmpty();
	}

	/**
	 * Returns the number of rows
	 */
	public int getRowCount() {
		return rows.size();
	}

	/**
	 * Returns the number of columns
	 */
	public int getColumnCount() {
		return colCount;
	}

	/**
	 * Removes unused space in the Vector that stores the rows.
	 */
	public void trimToSize() {
		rows.trimToSize();
	}

	/**
	 * Creates a linked list from an array.
	 */
	protected LinkedList<ListNode<E>> getLinkedListFromList(List<E> data) {
		LinkedList<ListNode<E>> rowData = new LinkedList<ListNode<E>>();

		// Change the array to a linked list
		for(int i=0; i<data.size(); i++) {

			// Do not all null data to the linked list
			if(data.get(i) != null) {
				ListNode<E> colNode = new ListNode<E>(i, data.get(i));
				rowData.add(colNode);
			}
		}

		return rowData;
	}

	/**
	 * This is a node in the linked list used to store column data.
	 * It stores the data and column number.
	 */
	protected class ListNode<E> implements Serializable {

		protected int colNumber;
		protected E data;

		public ListNode(int colNumber, E data) {
			this.colNumber = colNumber;
			setData(data);
		}

		/**
		 * Set the data for this node.
		 */
		public void setData(E data) {

			if(data instanceof String) {
				// Get the string from the pool
				data = (E)stringPool.getString((String)data);
			}

			this.data = data;
		}

		/**
		 * Get the data for this node.
		 */
		public E getData() {
			return data;
		}

		/**
		 * Get the col number for this node.
		 */
		public int getCol() {
			return colNumber;
		}
	}
}
